import sys
import os
import threading
import time
import logging
import socket
import subprocess
import re
import queue
from datetime import datetime
import requests
import uiautomator2 as u2
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QPushButton, QLabel, QTextEdit, QLineEdit, QCheckBox, QComboBox,
                             QGridLayout, QGroupBox, QFormLayout, QMessageBox, QMenu, QFrame,
                             QScrollArea, QSizePolicy, QProgressBar)
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QObject, QMutex
from PyQt5.QtGui import QFont, QColor, QTextCursor, QCursor

# ===== ДОБАВЛЕННЫЙ КОД ДЛЯ СКРЫТИЯ КОНСОЛИ =====
def hide_console():
    """Скрывает консоль на Windows"""
    try:
        import ctypes
        kernel32 = ctypes.windll.kernel32
        user32 = ctypes.windll.user32
        
        # Получаем дескриптор консольного окна
        console_window = kernel32.GetConsoleWindow()
        
        if console_window:
            # Скрываем консольное окно
            user32.ShowWindow(console_window, 0)  # 0 = SW_HIDE
            print("Консоль скрыта")
    except Exception as e:
        print(f"Не удалось скрыть консоль: {e}")

# Скрываем консоль сразу при запуске
hide_console()
# ===== КОНЕЦ ДОБАВЛЕННОГО КОДА =====

# Настройка логирования
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Глобальные переменные
successful_attempts = 0
failed_attempts = 0
batch_success_count = 0
pause_event = threading.Event()
pause_event.set()
is_processing = False
error_attempt_counter = 0
auto_paused_due_to_wifi = False

# Счетчики для различных типов ошибок
invalid_login_count = 0
captcha_count = 0
token_not_found_count = 0
timeout_count = 0
element_not_found_count = 0
unexpected_error_count = 0

all_results = []
file_lock = threading.Lock()

# Очереди для логов
log_queue = queue.Queue()
token_queue = queue.Queue()
failed_queue = queue.Queue()

# Пути к файлам
tokens_file = r"tokens.txt"
failed_logins_file = r"неуспешные логи.txt"
login_pass_file = r"log pass.txt"
driver_path = r"chromedriver_win64\chromedriver.exe"
last_limit_file = r"last limit.txt"
hide_browser_state_file = r"hide_browser_state.txt"
last_model_file = r"last_model.txt"
run_scenario_start_state_file = r"run_scenario_start_state.txt"
run_scenario_end_state_file = r"run_scenario_end_state.txt"
tokens_autoscroll_state_file = r"tokens_autoscroll_state.txt"
failed_autoscroll_state_file = r"failed_autoscroll_state.txt"

devices_by_model = {}
available_wifi_networks = []

# Функция для форматирования времени в формат ЧЧ:ММ:СС
def format_time(seconds):
    """Форматирует время из секунд в формат ЧЧ:ММ:СС"""
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    seconds = int(seconds % 60)
    return f"{hours:02d}:{minutes:02d}:{seconds:02d}"

# Сигналы для обновления GUI из потоков
class Signals(QObject):
    log_message = pyqtSignal(str, str)  # message, msg_type
    update_labels = pyqtSignal()
    append_token = pyqtSignal(str)
    append_failed_login = pyqtSignal(str, str)
    wifi_status_updated = pyqtSignal(str)
    ip_location_updated = pyqtSignal(str, str)
    device_scan_completed = pyqtSignal(list)
    wifi_scan_completed = pyqtSignal(list, str)
    update_progress = pyqtSignal(int, int)  # current, total

signals = Signals()

# Поток для обработки основных логов
class LogWorker(QThread):
    log_ready = pyqtSignal(str, str)
    
    def __init__(self):
        super().__init__()
        self.running = True
        
    def run(self):
        while self.running:
            try:
                message, msg_type = log_queue.get(timeout=0.1)
                self.log_ready.emit(message, msg_type)
                log_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Ошибка в LogWorker: {e}")
                
    def stop(self):
        self.running = False

# Поток для обработки токенов
class TokenWorker(QThread):
    token_ready = pyqtSignal(str)
    
    def __init__(self):
        super().__init__()
        self.running = True
        
    def run(self):
        while self.running:
            try:
                token = token_queue.get(timeout=0.1)
                self.token_ready.emit(token)
                token_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Ошибка в TokenWorker: {e}")
                
    def stop(self):
        self.running = False

# Поток для обработки неуспешных логинов
class FailedWorker(QThread):
    failed_ready = pyqtSignal(str, str)
    
    def __init__(self):
        super().__init__()
        self.running = True
        
    def run(self):
        while self.running:
            try:
                login, password = failed_queue.get(timeout=0.1)
                self.failed_ready.emit(login, password)
                failed_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Ошибка в FailedWorker: {e}")
                
    def stop(self):
        self.running = False

# Функции для добавления сообщений в очереди
def queue_log_message(message, msg_type="info"):
    try:
        log_queue.put_nowait((message, msg_type))
    except queue.Full:
        pass  # Игнорируем если очередь переполнена

def queue_token_message(token):
    try:
        token_queue.put_nowait(token)
    except queue.Full:
        pass

def queue_failed_message(login, password):
    try:
        failed_queue.put_nowait((login, password))
    except queue.Full:
        pass

# Рабочий поток для обработки аккаунтов
class ProcessingThread(QThread):
    def __init__(self, main_window):
        super().__init__()
        self.main_window = main_window
        self.current_account = 0
        self.total_accounts = 0

    def run(self):
        global is_processing
        try:
            if self.main_window.run_scenario_start_check.isChecked():
                queue_log_message("Запуск сценария в начале.", "info")
                self.main_window.run_flight_mode_scenario()
            self.process_all_accounts()
        except Exception as ex:
            queue_log_message(f"Ошибка в основном потоке: {ex}", "error")
        finally:
            is_processing = False

    def process_all_accounts(self):
        accounts = read_login_passwords()
        self.total_accounts = len(accounts)
        signals.update_progress.emit(0, self.total_accounts)
        
        for i, (login, password) in enumerate(accounts):
            pause_event.wait()
            if not is_processing:
                break
                
            self.current_account = i + 1
            signals.update_progress.emit(self.current_account, self.total_accounts)
            
            queue_log_message(f"Обработка аккаунта {self.current_account}/{self.total_accounts}: {login}", "info")
            self.main_window.open_browser(login, password)
        
        if is_processing and self.main_window.run_scenario_end_check.isChecked():
            queue_log_message("Запуск сценария в конце.", "info")
            self.main_window.run_flight_mode_scenario()

class ClickableLabel(QLabel):
    """Кликабельная метка с подчёркиванием"""
    clicked = pyqtSignal()
    
    def __init__(self, text="", parent=None):
        super().__init__(text, parent)
        self.setCursor(QCursor(Qt.PointingHandCursor))
        self.setStyleSheet("""
            QLabel {
                color: #b8c0d0;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffffff;
                background-color: rgba(62, 70, 86, 0.3);
                border-radius: 2px;
            }
        """)
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.clicked.emit()
        super().mousePressEvent(event)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.processing_thread = None
        self.timer_start_time = None
        self.total_elapsed_time = 0
        self.is_timer_running = False
        
        # Переменные для автопрокрутки
        self.tokens_autoscroll_enabled = True
        self.failed_autoscroll_enabled = True
        
        # Инициализация рабочих потоков для логов
        self.log_worker = LogWorker()
        self.token_worker = TokenWorker()
        self.failed_worker = FailedWorker()
        
        self.init_ui()
        self.center_window()
        self.setup_timers()
        self.connect_signals()
        self.setup_log_workers()
        self.load_settings()
        self.setup_context_menus()
        
    def setup_log_workers(self):
        """Настройка рабочих потоков для логов"""
        # Подключаем сигналы от рабочих потоков
        self.log_worker.log_ready.connect(self.log_message_direct)
        self.token_worker.token_ready.connect(self.append_token_direct)
        self.failed_worker.failed_ready.connect(self.append_failed_login_direct)
        
        # Запускаем рабочие потоки
        self.log_worker.start()
        self.token_worker.start()
        self.failed_worker.start()
        
    def center_window(self):
        """Центрирование окна на экране"""
        screen_geometry = QApplication.desktop().availableGeometry()
        screen_center_x = screen_geometry.width() // 2
        screen_center_y = screen_geometry.height() // 2
        
        window_width = 1200
        window_height = 950
        
        self.resize(window_width, window_height)
        
        x = screen_center_x - window_width // 2
        y = screen_center_y - window_height // 2
        
        self.move(x, y)
        
    def init_ui(self):
        self.setWindowTitle("Получение токенов ВК (PyQt5)")
        self.setStyleSheet("""
            QMainWindow {
                background-color: #1e222a;
                color: #b8c0d0;
            }
            QGroupBox {
                font-weight: bold;
                border: 2px solid #3e4656;
                border-radius: 5px;
                margin-top: 1ex;
                padding-top: 10px;
                background-color: #262a33;
                color: #b8c0d0;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
            QPushButton {
                background-color: #3e4656;
                color: #b8c0d0;
                border: 1px solid #4e566a;
                padding: 8px;
                border-radius: 4px;
                font: 10pt "Segoe UI";
            }
            QPushButton:hover {
                background-color: #4e566a;
            }
            QPushButton:pressed {
                background-color: #2a2f38;
            }
            QPushButton:disabled {
                background-color: #2a2f38;
                color: #6b7280;
            }
            QLineEdit, QComboBox {
                background-color: #2a2f38;
                color: #b8c0d0;
                border: 1px solid #3e4656;
                padding: 5px;
                border-radius: 3px;
            }
            QTextEdit {
                background-color: #2a2f38;
                color: #b8c0d0;
                border: 1px solid #3e4656;
                border-radius: 3px;
            }
            QCheckBox {
                color: #b8c0d0;
            }
            QLabel {
                color: #b8c0d0;
            }
            QProgressBar {
                background-color: #2a2f38;
                border: 1px solid #3e4656;
                border-radius: 3px;
                text-align: center;
                color: #b8c0d0;
            }
            QProgressBar::chunk {
                background-color: #5b9bd5;
                border-radius: 2px;
            }
        """)
        
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(10)
        main_layout.setContentsMargins(10, 10, 10, 10)
        
        # Панель управления
        self.create_control_panel(main_layout)
        
        # Статистика
        self.create_status_panel(main_layout)
        
        # Прогресс бар
        self.create_progress_panel(main_layout)
        
        # Логи
        self.create_logs_panel(main_layout)
        
    def create_control_panel(self, parent_layout):
        control_group = QGroupBox("Панель управления")
        control_layout = QVBoxLayout(control_group)
        
        # Первая строка кнопок
        row1_layout = QHBoxLayout()
        
        self.start_button = QPushButton("Запустить")
        # Специальные стили для кнопки "Запустить" с динамическим hover
        self.start_button.setStyleSheet("""
            QPushButton {
                background-color: #5b9bd5;
                color: #b8c0d0;
                border: 1px solid #4e566a;
                padding: 8px;
                border-radius: 4px;
                font: 10pt "Segoe UI";
            }
            QPushButton:enabled:hover {
                background-color: #4a8bc2;
                border: 1px solid #5b9bd5;
                color: #ffffff;
            }
            QPushButton:enabled:pressed {
                background-color: #3a7ba8;
            }
            QPushButton:disabled {
                background-color: #2a2f38;
                color: #6b7280;
                border: 1px solid #3e4656;
            }
        """)
        self.start_button.clicked.connect(self.start_browser_thread)
        row1_layout.addWidget(self.start_button)
        
        self.pause_button = QPushButton("Приостановить")
        self.pause_button.setEnabled(False)
        self.pause_button.clicked.connect(self.pause_processing)
        row1_layout.addWidget(self.pause_button)
        
        self.resume_button = QPushButton("Восстановить")
        self.resume_button.setEnabled(False)
        self.resume_button.clicked.connect(self.resume_processing)
        row1_layout.addWidget(self.resume_button)
        
        self.restart_button = QPushButton("Перезапустить ошибки")
        self.restart_button.clicked.connect(self.restart_failed_accounts)
        row1_layout.addWidget(self.restart_button)
        
        self.update_labels_button = QPushButton("Обновить метки")
        self.update_labels_button.clicked.connect(self.update_status_labels)
        row1_layout.addWidget(self.update_labels_button)
        
        self.clear_logs_button = QPushButton("Очистить все журналы")
        self.clear_logs_button.clicked.connect(self.clear_all_logs)
        row1_layout.addWidget(self.clear_logs_button)
        
        row1_layout.addStretch()
        control_layout.addLayout(row1_layout)
        
        # Вторая строка - чекбоксы
        row2_layout = QHBoxLayout()
        
        self.hide_browser_check = QCheckBox("Скрыть браузер")
        row2_layout.addWidget(self.hide_browser_check)
        
        self.run_scenario_start_check = QCheckBox("Запустить сценарий в начале")
        row2_layout.addWidget(self.run_scenario_start_check)
        
        self.run_scenario_end_check = QCheckBox("Запустить сценарий в конце")
        row2_layout.addWidget(self.run_scenario_end_check)
        
        # Лимит токенов
        limit_label = QLabel("Лимит токенов:")
        row2_layout.addWidget(limit_label)
        
        self.limit_entry = QLineEdit()
        self.limit_entry.setMaximumWidth(60)
        self.limit_entry.setText("0")
        row2_layout.addWidget(self.limit_entry)
        
        row2_layout.addStretch()
        control_layout.addLayout(row2_layout)
        
        # Третья строка - устройства и Wi-Fi
        row3_layout = QHBoxLayout()
        
        self.device_combobox = QComboBox()
        self.device_combobox.addItem("Нет подключённых устройств")
        self.device_combobox.currentTextChanged.connect(self.on_model_selected)
        row3_layout.addWidget(self.device_combobox)
        
        self.scan_devices_button = QPushButton("Сканировать устройства")
        self.scan_devices_button.clicked.connect(self.scan_devices_async)
        row3_layout.addWidget(self.scan_devices_button)
        
        wifi_label = QLabel("Выбрать Wi-Fi:")
        row3_layout.addWidget(wifi_label)
        
        self.wifi_combobox = QComboBox()
        self.wifi_combobox.setMinimumWidth(200)  # Устанавливаем минимальную ширину
        self.wifi_combobox.currentTextChanged.connect(self.on_wifi_selected)
        row3_layout.addWidget(self.wifi_combobox)
        
        self.refresh_wifi_button = QPushButton("Обновить Wi-Fi")
        self.refresh_wifi_button.clicked.connect(self.refresh_wifi_async)
        row3_layout.addWidget(self.refresh_wifi_button)
        
        row3_layout.addStretch()
        control_layout.addLayout(row3_layout)
        
        parent_layout.addWidget(control_group)
        
    def create_status_panel(self, parent_layout):
        status_group = QGroupBox("Статус")
        status_layout = QVBoxLayout(status_group)
        
        # Основная статистика
        stats_layout = QHBoxLayout()
        
        # Используем ClickableLabel для кликабельных меток
        self.success_label = ClickableLabel("Успешные попытки: 0")
        self.success_label.setStyleSheet("""
            QLabel {
                color: #89ca78;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #a4d9a0;
                background-color: rgba(137, 202, 120, 0.2);
                border-radius: 2px;
            }
        """)
        self.success_label.clicked.connect(self.copy_pure_successful_tokens)
        stats_layout.addWidget(self.success_label)
        
        self.failure_label = ClickableLabel("Неуспешные попытки: 0")
        self.failure_label.setStyleSheet("""
            QLabel {
                color: #e57373;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffaaaa;
                background-color: rgba(229, 115, 115, 0.2);
                border-radius: 2px;
            }
        """)
        self.failure_label.clicked.connect(self.copy_failed_logins)
        stats_layout.addWidget(self.failure_label)
        
        self.count_label = QLabel("Всего аккаунтов: 0")
        self.count_label.setStyleSheet("color: #5b9bd5;")
        stats_layout.addWidget(self.count_label)
        
        self.remaining_label = QLabel("Оставшиеся аккаунты: 0")
        self.remaining_label.setStyleSheet("color: #f4a261;")
        stats_layout.addWidget(self.remaining_label)
        
        copy_all_label = ClickableLabel("Copy all")
        copy_all_label.setStyleSheet("""
            QLabel {
                color: #b8c0d0;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffffff;
                background-color: rgba(184, 192, 208, 0.2);
                border-radius: 2px;
            }
        """)
        copy_all_label.clicked.connect(self.copy_all)
        stats_layout.addWidget(copy_all_label)
        
        copy_successful_label = ClickableLabel("Copy successful")
        copy_successful_label.setStyleSheet("""
            QLabel {
                color: #89ca78;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #a4d9a0;
                background-color: rgba(137, 202, 120, 0.2);
                border-radius: 2px;
            }
        """)
        copy_successful_label.clicked.connect(self.copy_successful_only)
        stats_layout.addWidget(copy_successful_label)
        
        stats_layout.addStretch()
        status_layout.addLayout(stats_layout)
        
        # Счетчики ошибок - первая строка (КЛИКАБЕЛЬНЫЕ)
        error_layout1 = QHBoxLayout()
        
        self.invalid_login_label = ClickableLabel("Невалидный логин/пароль: 0")
        self.invalid_login_label.setStyleSheet("""
            QLabel {
                color: #ff6b6b;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ff9999;
                background-color: rgba(255, 107, 107, 0.2);
                border-radius: 2px;
            }
        """)
        self.invalid_login_label.clicked.connect(self.copy_invalid_login_errors)
        error_layout1.addWidget(self.invalid_login_label)
        
        self.captcha_label = ClickableLabel("Капча: 0")
        self.captcha_label.setStyleSheet("""
            QLabel {
                color: #ff9f43;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffb366;
                background-color: rgba(255, 159, 67, 0.2);
                border-radius: 2px;
            }
        """)
        self.captcha_label.clicked.connect(self.copy_captcha_errors)
        error_layout1.addWidget(self.captcha_label)
        
        self.token_not_found_label = ClickableLabel("Токен не найден: 0")
        self.token_not_found_label.setStyleSheet("""
            QLabel {
                color: #feca57;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffd770;
                background-color: rgba(254, 202, 87, 0.2);
                border-radius: 2px;
            }
        """)
        self.token_not_found_label.clicked.connect(self.copy_token_not_found_errors)
        error_layout1.addWidget(self.token_not_found_label)
        
        error_layout1.addStretch()
        status_layout.addLayout(error_layout1)
        
        # Счетчики ошибок - вторая строка (КЛИКАБЕЛЬНЫЕ)
        error_layout2 = QHBoxLayout()
        
        self.timeout_label = ClickableLabel("Timeout: 0")
        self.timeout_label.setStyleSheet("""
            QLabel {
                color: #ff7675;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ff9999;
                background-color: rgba(255, 118, 117, 0.2);
                border-radius: 2px;
            }
        """)
        self.timeout_label.clicked.connect(self.copy_timeout_errors)
        error_layout2.addWidget(self.timeout_label)
        
        self.element_not_found_label = ClickableLabel("Элемент не найден: 0")
        self.element_not_found_label.setStyleSheet("""
            QLabel {
                color: #a29bfe;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #b8b3ff;
                background-color: rgba(162, 155, 254, 0.2);
                border-radius: 2px;
            }
        """)
        self.element_not_found_label.clicked.connect(self.copy_element_not_found_errors)
        error_layout2.addWidget(self.element_not_found_label)
        
        self.unexpected_error_label = ClickableLabel("Неожиданные ошибки: 0")
        self.unexpected_error_label.setStyleSheet("""
            QLabel {
                color: #fd79a8;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ff99cc;
                background-color: rgba(253, 121, 168, 0.2);
                border-radius: 2px;
            }
        """)
        self.unexpected_error_label.clicked.connect(self.copy_unexpected_errors)
        error_layout2.addWidget(self.unexpected_error_label)
        
        error_layout2.addStretch()
        status_layout.addLayout(error_layout2)
        
        # Дополнительная информация
        info_layout = QHBoxLayout()
        
        self.total_time_label = QLabel("Общее время: 00:00:00")
        self.total_time_label.setStyleSheet("color: #f4a261;")
        info_layout.addWidget(self.total_time_label)
        
        self.wifi_status_label = QLabel("Подключенный Wi-Fi: Неизвестно")
        self.wifi_status_label.setStyleSheet("color: #bb77d1;")
        info_layout.addWidget(self.wifi_status_label)
        
        self.ip_label = QLabel("IP: Сканирование...")
        info_layout.addWidget(self.ip_label)
        
        self.location_label = QLabel("Локация: Сканирование...")
        info_layout.addWidget(self.location_label)
        
        info_layout.addStretch()
        status_layout.addLayout(info_layout)
        
        parent_layout.addWidget(status_group)
        
    def create_progress_panel(self, parent_layout):
        """Создание панели прогресса"""
        progress_group = QGroupBox("Прогресс выполнения")
        progress_layout = QVBoxLayout(progress_group)
        
        # Основной прогресс бар
        self.progress_bar = QProgressBar()
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat("Ожидание запуска...")
        progress_layout.addWidget(self.progress_bar)
        
        # Информация о прогрессе
        progress_info_layout = QHBoxLayout()
        
        self.progress_label = QLabel("Готов к запуску")
        self.progress_label.setStyleSheet("color: #b8c0d0;")
        progress_info_layout.addWidget(self.progress_label)
        
        progress_info_layout.addStretch()
        progress_layout.addLayout(progress_info_layout)
        
        parent_layout.addWidget(progress_group)
        
    def create_logs_panel(self, parent_layout):
        logs_layout = QHBoxLayout()
        
        # Журнал действий
        log_group = QGroupBox("Журнал действий")
        log_layout = QVBoxLayout(log_group)
        
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setMinimumHeight(300)
        log_layout.addWidget(self.log_text)
        logs_layout.addWidget(log_group)
        
        # Успешные токены
        tokens_group = QGroupBox("Успешные токены")
        tokens_layout = QVBoxLayout(tokens_group)
        
        self.successful_tokens_text = QTextEdit()
        self.successful_tokens_text.setReadOnly(True)
        self.successful_tokens_text.setMinimumHeight(300)
        tokens_layout.addWidget(self.successful_tokens_text)
        logs_layout.addWidget(tokens_group)
        
        parent_layout.addLayout(logs_layout)
        
        # Неуспешные логины
        failed_group = QGroupBox("Неуспешные логины и пароли")
        failed_layout = QVBoxLayout(failed_group)
        
        self.failed_logins_text = QTextEdit()
        self.failed_logins_text.setReadOnly(True)
        self.failed_logins_text.setMaximumHeight(150)
        failed_layout.addWidget(self.failed_logins_text)
        
        parent_layout.addWidget(failed_group)
        
    def setup_timers(self):
        # Таймер для обновления времени работы
        self.runtime_timer = QTimer()
        self.runtime_timer.timeout.connect(self.update_runtime_display)
        
        # Таймер для проверки Wi-Fi
        self.wifi_timer = QTimer()
        self.wifi_timer.timeout.connect(self.update_wifi_status)
        self.wifi_timer.start(2000)  # Каждые 2 секунды
        
        # Таймер для обновления IP и локации
        self.ip_timer = QTimer()
        self.ip_timer.timeout.connect(self.update_ip_location_display)
        self.ip_timer.start(15000)  # Каждые 15 секунд
        
    def setup_context_menus(self):
        # Контекстное меню для логов
        self.log_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.log_text.customContextMenuRequested.connect(self.show_log_context_menu)
        
        self.successful_tokens_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.successful_tokens_text.customContextMenuRequested.connect(self.show_tokens_context_menu)
        
        self.failed_logins_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.failed_logins_text.customContextMenuRequested.connect(self.show_failed_context_menu)
        
    def connect_signals(self):
        signals.log_message.connect(self.queue_log_message_wrapper)
        signals.update_labels.connect(self.update_status_labels)
        signals.append_token.connect(self.queue_token_wrapper)
        signals.append_failed_login.connect(self.queue_failed_wrapper)
        signals.wifi_status_updated.connect(self.wifi_status_label.setText)
        signals.ip_location_updated.connect(self.update_ip_location_labels)
        signals.device_scan_completed.connect(self.update_device_list)
        signals.wifi_scan_completed.connect(self.update_wifi_list)
        signals.update_progress.connect(self.update_progress_bar)
        
    def update_progress_bar(self, current, total):
        """Обновление прогресс бара"""
        if total > 0:
            percentage = int((current / total) * 100)
            self.progress_bar.setValue(percentage)
            self.progress_bar.setFormat(f"{current}/{total} ({percentage}%)")
            self.progress_label.setText(f"Обработано: {current} из {total} аккаунтов")
        else:
            self.progress_bar.setValue(0)
            self.progress_bar.setFormat("Ожидание...")
            self.progress_label.setText("Готов к запуску")
        
    def queue_log_message_wrapper(self, message, msg_type):
        """Обертка для добавления лог-сообщений в очередь"""
        queue_log_message(message, msg_type)
        
    def queue_token_wrapper(self, token):
        """Обертка для добавления токенов в очередь"""
        queue_token_message(token)
        
    def queue_failed_wrapper(self, login, password):
        """Обертка для добавления неуспешных логинов в очередь"""
        queue_failed_message(login, password)
        
    def log_message_direct(self, message, msg_type="info"):
        """Прямое добавление сообщения в лог (вызывается из рабочего потока)"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        full_message = f"[{timestamp}] {message}"
        
        cursor = self.log_text.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.log_text.setTextCursor(cursor)
        
        if msg_type == "info":
            self.log_text.setTextColor(QColor("#b8c0d0"))
        elif msg_type == "success":
            self.log_text.setTextColor(QColor("#89ca78"))
        elif msg_type == "error":
            self.log_text.setTextColor(QColor("#e57373"))
        
        self.log_text.insertPlainText(full_message + "\n")
        self.log_text.ensureCursorVisible()
        
    def append_token_direct(self, token):
        """Прямое добавление токена (вызывается из рабочего потока)"""
        self.successful_tokens_text.append(token)
        if self.tokens_autoscroll_enabled:
            self.successful_tokens_text.ensureCursorVisible()
        
    def append_failed_login_direct(self, login, password):
        """Прямое добавление неуспешного логина (вызывается из рабочего потока)"""
        self.failed_logins_text.append(f"{login}:{password}")
        if self.failed_autoscroll_enabled:
            self.failed_logins_text.ensureCursorVisible()
            
    def load_settings(self):
        # Загрузка сохраненных настроек
        self.hide_browser_check.setChecked(self.load_hide_browser_state())
        self.run_scenario_start_check.setChecked(self.load_run_scenario_start_state())
        self.run_scenario_end_check.setChecked(self.load_run_scenario_end_state())
        self.limit_entry.setText(self.load_last_limit())
        
        # Загрузка состояния автопрокрутки
        self.tokens_autoscroll_enabled = self.load_tokens_autoscroll_state()
        self.failed_autoscroll_enabled = self.load_failed_autoscroll_state()
        
        # Инициализация
        queue_log_message("Журнал действий:", "info")
        self.update_status_labels()
        self.scan_devices_async()
        self.refresh_wifi_async()
        self.update_ip_location_display()
        
    def reset_timer(self):
        """Сброс таймера до нуля"""
        self.total_elapsed_time = 0
        self.timer_start_time = None
        self.is_timer_running = False
        self.runtime_timer.stop()
        self.total_time_label.setText("Общее время: 00:00:00")
        queue_log_message("Таймер сброшен", "info")
        
    def update_status_labels(self):
        accounts = read_login_passwords()
        total_accounts = len(accounts)
        remaining = total_accounts - (successful_attempts + failed_attempts)
        
        self.success_label.setText(f"Успешные попытки: {successful_attempts}")
        self.failure_label.setText(f"Неуспешные попытки: {failed_attempts}")
        self.count_label.setText(f"Всего аккаунтов: {total_accounts}")
        self.remaining_label.setText(f"Оставшиеся аккаунты: {remaining}")
        
        # Обновляем счетчики ошибок
        self.invalid_login_label.setText(f"Невалидный логин/пароль: {invalid_login_count}")
        self.captcha_label.setText(f"Капча: {captcha_count}")
        self.token_not_found_label.setText(f"Токен не найден: {token_not_found_count}")
        self.timeout_label.setText(f"Timeout: {timeout_count}")
        self.element_not_found_label.setText(f"Элемент не найден: {element_not_found_count}")
        self.unexpected_error_label.setText(f"Неожиданные ошибки: {unexpected_error_count}")
        
    def start_browser_thread(self):
        global is_processing
        if is_processing:
            QMessageBox.information(self, "Информация", "Процесс уже запущен.")
            return
            
        add_token_separator()
        
        if self.wifi_combobox.currentText() != get_connected_wifi():
            QMessageBox.warning(self, "Внимание", 
                               "Текущая Wi-Fi сеть не совпадает с выбранной.\nЗапуск невозможен.")
            return
            
        self.save_last_limit()
        is_processing = True
        pause_event.set()
        
        self.start_button.setEnabled(False)
        self.pause_button.setEnabled(True)
        self.resume_button.setEnabled(False)
        
        # Сброс прогресс бара
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat("Инициализация...")
        self.progress_label.setText("Подготовка к запуску...")
        
        # Сброс таймера только при запуске новой сессии
        self.reset_timer()
        
        # Запуск таймера
        self.start_timer()
        
        # Запуск обработки в отдельном потоке
        self.processing_thread = ProcessingThread(self)
        self.processing_thread.finished.connect(self.on_processing_finished)
        self.processing_thread.start()
        
    def pause_processing(self):
        global auto_paused_due_to_wifi
        if not is_processing:
            return
        pause_event.clear()
        auto_paused_due_to_wifi = False
        queue_log_message("Процесс приостановлен (вручную).", "info")
        self.pause_button.setEnabled(False)
        self.resume_button.setEnabled(True)
        self.stop_timer()
        
    def resume_processing(self):
        if not is_processing:
            return
        if self.wifi_combobox.currentText() != get_connected_wifi():
            QMessageBox.warning(self, "Внимание", 
                               "Невозможно возобновить: текущая Wi-Fi сеть не совпадает с выбранной.")
            return
        pause_event.set()
        queue_log_message("Процесс восстановлен (вручную).", "info")
        self.pause_button.setEnabled(True)
        self.resume_button.setEnabled(False)
        self.start_timer()
        
    def on_processing_finished(self):
        global is_processing
        is_processing = False
        self.start_button.setEnabled(True)
        self.pause_button.setEnabled(False)
        self.resume_button.setEnabled(False)
        self.stop_timer()
        
        # Завершение прогресс бара
        self.progress_bar.setFormat("Завершено")
        self.progress_label.setText("Обработка завершена")
        
    def restart_failed_accounts(self):
        global successful_attempts, failed_attempts, batch_success_count
        global invalid_login_count, captcha_count, token_not_found_count
        global timeout_count, element_not_found_count, unexpected_error_count
        
        # Сброс всех счетчиков
        successful_attempts = 0
        failed_attempts = 0
        batch_success_count = 0
        invalid_login_count = 0
        captcha_count = 0
        token_not_found_count = 0
        timeout_count = 0
        element_not_found_count = 0
        unexpected_error_count = 0
        
        all_results.clear()
        self.update_status_labels()
        self.successful_tokens_text.clear()
        
        content = self.failed_logins_text.toPlainText().strip()
        if not content:
            QMessageBox.information(self, "Информация", "Нет аккаунтов для перезапуска.")
            return
            
        self.failed_logins_text.clear()
        
        try:
            with open(failed_logins_file, "w", encoding="utf-8") as f:
                f.write("")
        except Exception as e:
            queue_log_message(f"Ошибка при очистке файла неуспешных логинов: {e}", "error")
            
        def process_failed():
            lines = content.splitlines()
            for line in lines:
                parts = line.split(":", 1)
                if len(parts) == 2:
                    login, password = parts
                    queue_log_message(f"Перезапуск аккаунта: {login}", "info")
                    self.open_browser(login, password)
            signals.update_labels.emit()
            
        thread = threading.Thread(target=process_failed, daemon=True)
        thread.start()
        
    def clear_all_logs(self):
        reply = QMessageBox.question(self, "Подтверждение", 
                                   "Вы уверены, что хотите очистить журналы в интерфейсе?\n(Файлы tokens.txt и неуспешные логи.txt не будут затронуты)",
                                   QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                self.log_text.clear()
                self.successful_tokens_text.clear()
                self.failed_logins_text.clear()
            
                global successful_attempts, failed_attempts
                global invalid_login_count, captcha_count, token_not_found_count
                global timeout_count, element_not_found_count, unexpected_error_count
            
                successful_attempts = 0
                failed_attempts = 0
                invalid_login_count = 0
                captcha_count = 0
                token_not_found_count = 0
                timeout_count = 0
                element_not_found_count = 0
                unexpected_error_count = 0
            
                all_results.clear()
                self.update_status_labels()
                
                # Таймер не сбрасывается при очистке логов, только если он не работает
                if not self.is_timer_running:
                    self.reset_timer()
                
                # Сброс прогресс бара
                self.progress_bar.setValue(0)
                self.progress_bar.setFormat("Ожидание запуска...")
                self.progress_label.setText("Готов к запуску")
                
                queue_log_message("Журналы интерфейса очищены (файлы tokens.txt и неуспешные логи.txt сохранены).", "info")
            except Exception as e:
                QMessageBox.critical(self, "Ошибка", f"Не удалось очистить журналы: {e}")
        
    def copy_pure_successful_tokens(self):
        """Копирование только чистых успешных токенов (без ошибок)"""
        if not all_results:
            queue_log_message("Нет данных для копирования успешных токенов.", "info")
            return
            
        error_keywords = [
            "Невалидный логин или пароль",
            "Найдена капча", 
            "Токен не найден",
            "Timeout",
            "Элемент не найден",
            "Ошибка:",
            "Неожиданная ошибка",
            "Неизвестная ошибка"
        ]
        
        successful_tokens = []
        for login, password, result in all_results:
            is_error = any(keyword in result for keyword in error_keywords)
            # Проверяем что это токен (длинный и без ошибок)
            if not is_error and len(result) > 30 and ":" not in result:
                successful_tokens.append(result)
                
        if not successful_tokens:
            queue_log_message("Нет чистых успешных токенов для копирования.", "info")
            return
            
        text = "\n".join(successful_tokens)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(successful_tokens)} чистых успешных токенов в буфер обмена.", "info")
        
    def copy_failed_logins(self):
        content = self.failed_logins_text.toPlainText().strip()
        if not content:
            QMessageBox.information(self, "Информация", "Нет неуспешных логинов для копирования.")
            return
        QApplication.clipboard().setText(content)
        queue_log_message("Неуспешные логины и пароли скопированы в буфер обмена.", "info")
        
    def copy_all(self):
        if not all_results:
            QMessageBox.information(self, "Информация", "Нет данных для копирования.")
            return
        lines = [f"{login}:{password}\t{result}" for login, password, result in all_results]
        text = "\n".join(lines)
        QApplication.clipboard().setText(text)
        queue_log_message("Список логин:пароль и результатов скопирован.", "info")
        
    def copy_successful_only(self):
        if not all_results:
            QMessageBox.information(self, "Информация", "Нет данных для копирования.")
            return
            
        error_keywords = [
            "Невалидный логин или пароль",
            "Найдена капча", 
            "Токен не найден",
            "Timeout",
            "Элемент не найден",
            "Ошибка:",
            "Неожиданная ошибка",
            "Неизвестная ошибка"
        ]
        
        successful_results = []
        for login, password, result in all_results:
            is_error = any(keyword in result for keyword in error_keywords)
            if not is_error and len(result) > 10:
                successful_results.append((login, password, result))
                
        if not successful_results:
            QMessageBox.information(self, "Информация", "Нет успешных результатов для копирования.")
            return
            
        lines = [f"{login}:{password}\t{result}" for login, password, result in successful_results]
        text = "\n".join(lines)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(successful_results)} успешных результатов в буфер обмена.", "info")
        
    # МЕТОДЫ ДЛЯ КОПИРОВАНИЯ ОШИБОК ПО ТИПАМ
    def copy_invalid_login_errors(self):
        """Копирование ошибок невалидного логина/пароля"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок невалидного логина.", "info")
            return
            
        invalid_results = []
        for login, password, result in all_results:
            if "Невалидный логин или пароль" in result:
                invalid_results.append(f"{login}:{password}\t{result}")
                
        if not invalid_results:
            queue_log_message("Нет ошибок невалидного логина/пароля для копирования.", "info")
            return
            
        text = "\n".join(invalid_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(invalid_results)} ошибок невалидного логина/пароля.", "info")
        
    def copy_captcha_errors(self):
        """Копирование ошибок капчи"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок капчи.", "info")
            return
            
        captcha_results = []
        for login, password, result in all_results:
            if "Найдена капча" in result or "капча" in result.lower():
                captcha_results.append(f"{login}:{password}\t{result}")
                
        if not captcha_results:
            queue_log_message("Нет ошибок капчи для копирования.", "info")
            return
            
        text = "\n".join(captcha_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(captcha_results)} ошибок капчи.", "info")
        
    def copy_token_not_found_errors(self):
        """Копирование ошибок 'токен не найден'"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок 'токен не найден'.", "info")
            return
            
        token_not_found_results = []
        for login, password, result in all_results:
            if "Токен не найден" in result:
                token_not_found_results.append(f"{login}:{password}\t{result}")
                
        if not token_not_found_results:
            queue_log_message("Нет ошибок 'токен не найден' для копирования.", "info")
            return
            
        text = "\n".join(token_not_found_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(token_not_found_results)} ошибок 'токен не найден'.", "info")
        
    def copy_timeout_errors(self):
        """Копирование ошибок timeout"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок timeout.", "info")
            return
            
        timeout_results = []
        for login, password, result in all_results:
            if "Timeout" in result:
                timeout_results.append(f"{login}:{password}\t{result}")
                
        if not timeout_results:
            queue_log_message("Нет ошибок timeout для копирования.", "info")
            return
            
        text = "\n".join(timeout_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(timeout_results)} ошибок timeout.", "info")
        
    def copy_element_not_found_errors(self):
        """Копирование ошибок 'элемент не найден'"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок 'элемент не найден'.", "info")
            return
            
        element_not_found_results = []
        for login, password, result in all_results:
            if "Элемент не найден" in result:
                element_not_found_results.append(f"{login}:{password}\t{result}")
                
        if not element_not_found_results:
            queue_log_message("Нет ошибок 'элемент не найден' для копирования.", "info")
            return
            
        text = "\n".join(element_not_found_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(element_not_found_results)} ошибок 'элемент не найден'.", "info")
        
    def copy_unexpected_errors(self):
        """Копирование неожиданных ошибок"""
        if not all_results:
            queue_log_message("Нет данных для копирования неожиданных ошибок.", "info")
            return
            
        unexpected_results = []
        for login, password, result in all_results:
            if ("Ошибка:" in result or "Неожиданная ошибка" in result or "Неизвестная ошибка" in result) and \
               "Невалидный логин или пароль" not in result and \
               "Найдена капча" not in result and \
               "Токен не найден" not in result and \
               "Timeout" not in result and \
               "Элемент не найден" not in result:
                unexpected_results.append(f"{login}:{password}\t{result}")
                
        if not unexpected_results:
            queue_log_message("Нет неожиданных ошибок для копирования.", "info")
            return
            
        text = "\n".join(unexpected_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(unexpected_results)} неожиданных ошибок.", "info")
        
    def show_log_context_menu(self, pos):
        menu = QMenu()
        copy_action = menu.addAction("Копировать")
        select_all_action = menu.addAction("Выбрать всё")
        menu.addSeparator()
        autoscroll_action = menu.addAction("Автопрокрутка")
        autoscroll_action.setCheckable(True)
        autoscroll_action.setChecked(True)
        
        action = menu.exec_(self.log_text.mapToGlobal(pos))
        if action == copy_action:
            cursor = self.log_text.textCursor()
            if cursor.hasSelection():
                QApplication.clipboard().setText(cursor.selectedText())
        elif action == select_all_action:
            self.log_text.selectAll()
            
    def show_tokens_context_menu(self, pos):
        menu = QMenu()
        copy_action = menu.addAction("Копировать")
        select_all_action = menu.addAction("Выбрать всё")
        menu.addSeparator()
        autoscroll_action = menu.addAction("Автопрокрутка")
        autoscroll_action.setCheckable(True)
        autoscroll_action.setChecked(self.tokens_autoscroll_enabled)
        
        action = menu.exec_(self.successful_tokens_text.mapToGlobal(pos))
        if action == copy_action:
            cursor = self.successful_tokens_text.textCursor()
            if cursor.hasSelection():
                QApplication.clipboard().setText(cursor.selectedText())
        elif action == select_all_action:
            self.successful_tokens_text.selectAll()
        elif action == autoscroll_action:
            self.tokens_autoscroll_enabled = not self.tokens_autoscroll_enabled
            self.save_tokens_autoscroll_state()
            if self.tokens_autoscroll_enabled:
                queue_log_message("Автопрокрутка для токенов включена.", "info")
            else:
                queue_log_message("Автопрокрутка для токенов отключена.", "info")
                
    def show_failed_context_menu(self, pos):
        menu = QMenu()
        copy_action = menu.addAction("Копировать")
        select_all_action = menu.addAction("Выбрать всё")
        menu.addSeparator()
        autoscroll_action = menu.addAction("Автопрокрутка")
        autoscroll_action.setCheckable(True)
        autoscroll_action.setChecked(self.failed_autoscroll_enabled)
        
        action = menu.exec_(self.failed_logins_text.mapToGlobal(pos))
        if action == copy_action:
            cursor = self.failed_logins_text.textCursor()
            if cursor.hasSelection():
                QApplication.clipboard().setText(cursor.selectedText())
        elif action == select_all_action:
            self.failed_logins_text.selectAll()
        elif action == autoscroll_action:
            self.failed_autoscroll_enabled = not self.failed_autoscroll_enabled
            self.save_failed_autoscroll_state()
            if self.failed_autoscroll_enabled:
                queue_log_message("Автопрокрутка для неуспешных логинов включена.", "info")
            else:
                queue_log_message("Автопрокрутка для неуспешных логинов отключена.", "info")
            
    def start_timer(self):
        self.timer_start_time = time.time()
        self.is_timer_running = True
        self.runtime_timer.start(1000)  # Обновление каждую секунду
        
    def stop_timer(self):
        if self.is_timer_running and self.timer_start_time:
            self.total_elapsed_time += time.time() - self.timer_start_time
            self.is_timer_running = False
            self.runtime_timer.stop()
            
    def update_runtime_display(self):
        """Обновление отображения времени в формате ЧЧ:ММ:СС"""
        if self.is_timer_running and self.timer_start_time:
            current_time = self.total_elapsed_time + (time.time() - self.timer_start_time)
            formatted_time = format_time(current_time)
            self.total_time_label.setText(f"Время выполнения: {formatted_time}")
        else:
            formatted_time = format_time(self.total_elapsed_time)
            self.total_time_label.setText(f"Общее время: {formatted_time}")
            
    def update_wifi_status(self):
        current_wifi = get_connected_wifi()
        self.wifi_status_label.setText(f"Подключенный Wi-Fi: {current_wifi}")
        
        # Логика автопаузы при смене Wi-Fi
        selected_wifi = self.wifi_combobox.currentText()
        global auto_paused_due_to_wifi
        
        if selected_wifi and selected_wifi != current_wifi:
            if is_processing and pause_event.is_set():
                pause_event.clear()
                auto_paused_due_to_wifi = True
                queue_log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", "info")
            if not is_processing:
                self.start_button.setEnabled(False)
        else:
            if not is_processing:
                self.start_button.setEnabled(True)
            else:
                if auto_paused_due_to_wifi and not pause_event.is_set():
                    pause_event.set()
                    auto_paused_due_to_wifi = False
                    queue_log_message("Процесс возобновлён автоматически (Wi-Fi соответствует).", "info")
                    
    def update_ip_location_display(self):
        def worker():
            ip = get_external_ip()
            if ip != "Недоступно":
                location = get_location_by_ip(ip)
            else:
                location = "Недоступно"
            signals.ip_location_updated.emit(ip, location)
            
        thread = threading.Thread(target=worker, daemon=True)
        thread.start()
        
    def update_ip_location_labels(self, ip, location):
        self.ip_label.setText(f"IP: {ip}")
        self.location_label.setText(f"Локация: {location}")
        
    def scan_devices_async(self):
        def worker():
            global devices_by_model
            devices_by_model.clear()
            try:
                result = subprocess.run(["adb", "devices", "-l"], 
                                      capture_output=True, text=True, check=False)
                output = result.stdout.strip().splitlines()
                for line in output[1:]:
                    line = line.strip()
                    if not line or "offline" in line or "unauthorized" in line or "unknown" in line:
                        continue
                    parts = line.split()
                    serial = parts[0]
                    model = None
                    for p in parts:
                        if p.startswith("model:"):
                            model = p.split(":", 1)[1]
                            break
                    if not model:
                        model = "UnknownModel"
                    devices_by_model[model] = serial
                queue_log_message(f"Устройства обновлены: {devices_by_model}", "info")
            except Exception as e:
                queue_log_message(f"Ошибка при сканировании устройств: {e}", "error")
            
            model_list = list(devices_by_model.keys())
            signals.device_scan_completed.emit(model_list)
            
        thread = threading.Thread(target=worker, daemon=True)
        thread.start()
        
    def update_device_list(self, model_list):
        current_selection = self.load_last_model()
        self.device_combobox.clear()
        
        if model_list:
            self.device_combobox.addItems(model_list)
            if current_selection in model_list:
                self.device_combobox.setCurrentText(current_selection)
        else:
            self.device_combobox.addItem("Нет подключённых устройств")
            
    def refresh_wifi_async(self):
        def worker():
            global available_wifi_networks
            available_wifi_networks = get_available_networks()
            current_ssid = get_connected_wifi()
            signals.wifi_scan_completed.emit(available_wifi_networks, current_ssid)
            
        thread = threading.Thread(target=worker, daemon=True)
        thread.start()
        
    def update_wifi_list(self, networks, current_ssid):
        self.wifi_combobox.clear()
        if networks:
            self.wifi_combobox.addItems(networks)
            if current_ssid in networks:
                self.wifi_combobox.setCurrentText(current_ssid)
                
    def on_model_selected(self):
        model = self.device_combobox.currentText()
        self.save_last_model(model)
        
    def on_wifi_selected(self):
        global auto_paused_due_to_wifi
        selected = self.wifi_combobox.currentText()
        current_wifi = get_connected_wifi()
        
        if selected and selected != current_wifi:
            if is_processing and pause_event.is_set():
                pause_event.clear()
                auto_paused_due_to_wifi = True
                queue_log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", "info")
            if not is_processing:
                self.start_button.setEnabled(False)
        else:
            if not is_processing:
                self.start_button.setEnabled(True)
            else:
                if auto_paused_due_to_wifi and not pause_event.is_set():
                    pause_event.set()
                    auto_paused_due_to_wifi = False
                    queue_log_message("Процесс возобновлён автоматически (Wi-Fi выбран совпадает с текущей).", "info")
                    
    # Методы для работы с файлами настроек
    def get_limit_value(self):
        try:
            return int(self.limit_entry.text())
        except ValueError:
            return 0
            
    def save_last_limit(self):
        try:
            with open(last_limit_file, "w", encoding="utf-8") as f:
                f.write(self.limit_entry.text())
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении лимита: {e}", "error")
            
    def load_last_limit(self):
        try:
            with open(last_limit_file, "r", encoding="utf-8") as f:
                return f.read().strip()
        except Exception:
            return "0"
            
    def load_hide_browser_state(self):
        try:
            with open(hide_browser_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return False
            
    def load_run_scenario_start_state(self):
        try:
            with open(run_scenario_start_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return False
            
    def load_run_scenario_end_state(self):
        try:
            with open(run_scenario_end_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return False
            
    def save_last_model(self, model):
        try:
            with open(last_model_file, "w", encoding="utf-8") as f:
                f.write(model)
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении модели: {e}", "error")
            
    def load_last_model(self):
        try:
            with open(last_model_file, "r", encoding="utf-8") as f:
                return f.read().strip()
        except Exception:
            return ""
            
    # Методы для сохранения/загрузки состояния автопрокрутки
    def save_tokens_autoscroll_state(self):
        try:
            with open(tokens_autoscroll_state_file, "w", encoding="utf-8") as f:
                f.write(str(self.tokens_autoscroll_enabled).lower())
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении состояния автопрокрутки токенов: {e}", "error")
            
    def load_tokens_autoscroll_state(self):
        try:
            with open(tokens_autoscroll_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return True  # По умолчанию включено
            
    def save_failed_autoscroll_state(self):
        try:
            with open(failed_autoscroll_state_file, "w", encoding="utf-8") as f:
                f.write(str(self.failed_autoscroll_enabled).lower())
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении состояния автопрокрутки неуспешных логинов: {e}", "error")
            
    def load_failed_autoscroll_state(self):
        try:
            with open(failed_autoscroll_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return True  # По умолчанию включено
    
    def closeEvent(self, event):
        """Обработка закрытия приложения"""
        try:
            # Останавливаем рабочие потоки
            self.log_worker.stop()
            self.token_worker.stop()
            self.failed_worker.stop()
            
            # Ждем завершения потоков
            self.log_worker.wait(1000)  # максимум 1 секунда
            self.token_worker.wait(1000)
            self.failed_worker.wait(1000)
            
            # Сохраняем состояния чекбоксов
            try:
                with open(hide_browser_state_file, "w", encoding="utf-8") as f:
                    f.write(str(self.hide_browser_check.isChecked()).lower())
                with open(run_scenario_start_state_file, "w", encoding="utf-8") as f:
                    f.write(str(self.run_scenario_start_check.isChecked()).lower())
                with open(run_scenario_end_state_file, "w", encoding="utf-8") as f:
                    f.write(str(self.run_scenario_end_check.isChecked()).lower())
            except Exception as e:
                print(f"Ошибка при сохранении настроек: {e}")
                    
        except Exception as e:
            print(f"Ошибка при закрытии приложения: {e}")
        finally:
            event.accept()
            
    # Методы Selenium и обработки аккаунтов
    def open_browser(self, login, password):
        global successful_attempts, failed_attempts, batch_success_count
        global invalid_login_count, captcha_count, token_not_found_count
        global timeout_count, element_not_found_count, unexpected_error_count
    
        start_time = time.time()
        queue_log_message(f"Открытие браузера для: {login}", "info")
    
        # Флаг для определения нужно ли запускать сценарий
        should_run_scenario = False
    
        options = Options()
        options.add_argument("--incognito")
        if self.hide_browser_check.isChecked():
            options.add_argument("--headless")
            options.add_argument("--disable-gpu")
            queue_log_message("Скрытый режим браузера.", "info")
    
        service = Service(driver_path)
        driver = webdriver.Chrome(service=service, options=options)
    
        try:
            driver.get("https://vkhost.github.io/")
            queue_log_message("Страница vkhost открыта.", "info")
        
            button = WebDriverWait(driver, 15).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "button.btn[onclick='auth(6121396)']"))
            )
            button.click()
            queue_log_message("Кнопка авторизации нажата.", "info")
        
            WebDriverWait(driver, 15).until(lambda d: len(d.window_handles) > 1)
            driver.switch_to.window(driver.window_handles[-1])
            queue_log_message(f"Новая вкладка: {driver.current_url}", "info")
        
            WebDriverWait(driver, 15).until(
                lambda d: d.current_url.startswith("https://oauth.vk.com/authorize?")
            )
            queue_log_message(f"Страница ВК: {driver.current_url}", "info")
        
            email_field = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text'][name='email']"))
            )
            password_field = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='password'][name='pass']"))
            )
        
            email_field.send_keys(login)
            queue_log_message("Логин введён.", "info")
            password_field.send_keys(password)
            queue_log_message("Пароль введён.", "info")
        
            submit_button = WebDriverWait(driver, 15).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "button.flat_button.oauth_button.button_wide[type='submit']"))
            )
            submit_button.click()
            queue_log_message("Кнопка 'Войти' нажата.", "info")
        
            try:
                found_error = WebDriverWait(driver, 5).until(
                    EC.any_of(
                        EC.presence_of_element_located((By.CSS_SELECTOR, ".box_error")),
                        EC.presence_of_element_located((By.CSS_SELECTOR, ".oauth_captcha"))
                    )
                )
            
                error_class = found_error.get_attribute("class")
                if "box_error" in error_class:
                    error_text = "Невалидный логин или пароль."
                    invalid_login_count += 1
                elif "oauth_captcha" in error_class:
                    error_text = "Найдена капча"
                    captcha_count += 1
                else:
                    error_text = "Неизвестная ошибка"
                    unexpected_error_count += 1
                
                failed_attempts += 1
                save_token_to_file(f"{error_text}: {login}", is_error=True)
                save_failed_login(login, password)
                append_full_result(login, password, error_text)
                queue_log_message(f"Ошибка: {error_text}", "error")
                queue_token_message(f"{error_text}: {login}")
                return
            
            except TimeoutException:
                pass
            
            allow_button = WebDriverWait(driver, 15).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "button.flat_button[onclick='return allow(this);']"))
            )
            allow_button.click()
            queue_log_message("Кнопка подтверждения нажата.", "info")
        
            WebDriverWait(driver, 15).until(
                lambda d: d.current_url.startswith("https://oauth.vk.com/blank.html#access_token=")
            )
            queue_log_message(f"Страница с токеном: {driver.current_url}", "info")

            url = driver.current_url
            token_match = re.search(r"access_token=([^&]+)&expires_in", url)
            if token_match:
                token = token_match.group(1)
                save_token_to_file(token)
                append_full_result(login, password, token)
                successful_attempts += 1
                batch_success_count += 1
                queue_log_message("Токен получен.", "success")
            
                current_limit = self.get_limit_value()
                if current_limit > 0 and batch_success_count >= current_limit:
                    # Устанавливаем флаг для запуска сценария при достижении лимита
                    should_run_scenario = True
                    batch_success_count = 0
            else:
                failed_attempts += 1
                token_not_found_count += 1
                save_token_to_file(f"Токен не найден: {login}", is_error=True)
                save_failed_login(login, password)
                append_full_result(login, password, "Токен не найден")
                queue_log_message("Ошибка: Токен не найден.", "error")
                queue_token_message(f"Токен не найден: {login}")
            
        except TimeoutException:
            failed_attempts += 1
            timeout_count += 1
            save_token_to_file(f"Timeout: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, "Timeout")
            queue_log_message(f"Ошибка: Timeout. URL: {driver.current_url}", "error")
            queue_token_message(f"Timeout: {login}")
        
        except NoSuchElementException:
            failed_attempts += 1
            element_not_found_count += 1
            save_token_to_file(f"Элемент не найден: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, "Элемент не найден")
            queue_log_message("Ошибка: Элемент не найден.", "error")
            queue_token_message(f"Элемент не найден: {login}")
        
        except Exception as e:
            failed_attempts += 1
            unexpected_error_count += 1
            save_token_to_file(f"{str(e)}: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, f"Ошибка: {e}")
            queue_log_message(f"Неожиданная ошибка: {e}", "error")
            queue_token_message(f"Неожиданная ошибка: {login}")
        
        finally:
            driver.quit()
            queue_log_message("Браузер закрыт.", "info")
            signals.update_labels.emit()
            end_time = time.time()
            processing_time = end_time - start_time
            processing_time_formatted = format_time(processing_time)
            queue_log_message(f"Обработка аккаунта {login} завершена за {processing_time_formatted}.", "info")
        
            # Запуск сценария только при определенных условиях после закрытия браузера
            if should_run_scenario:
                queue_log_message("Запуск сценария смены IP после достижения лимита токенов.", "info")
                self.run_flight_mode_scenario()

    def run_flight_mode_scenario(self):
        chosen_model = self.device_combobox.currentText()
        if not chosen_model or chosen_model == "Нет подключённых устройств":
            queue_log_message("Не выбрана модель для сценария.", "error")
            return
            
        serial = devices_by_model.get(chosen_model)
        if not serial:
            queue_log_message(f"Не найден serial для '{chosen_model}'.", "error")
            return
            
        try:
            queue_log_message(f"Запуск сценария на '{chosen_model}'.", "info")
            d = u2.connect(serial)
            queue_log_message(f"Устройство: {d.device_info}", "info")
            
            # Открытие настроек режима полета
            subprocess.run(["adb", "-s", serial, "shell", "am", "start", "-a", "android.settings.AIRPLANE_MODE_SETTINGS"])
            time.sleep(0.3)
            
            # Переключение режима полета (выключить/включить)
            switch_element = d(resourceId="android:id/switch_widget")
            switch_element.click_exists(timeout=3)
            time.sleep(0.3)
            switch_element.click_exists(timeout=3)
            time.sleep(0.5)
            
            # Открытие настроек точки доступа
            d.app_start("com.android.settings", ".TetherSettings")
            time.sleep(0.3)
            if d(resourceId="com.android.settings:id/recycler_view").child(index=0).exists(timeout=2):
                d(resourceId="com.android.settings:id/recycler_view").child(index=0).click()
                time.sleep(0.3)
                
            # Возврат в главное меню
            for _ in range(3):
                d.press("back")
                time.sleep(0.2)
                
            queue_log_message("Ожидание Wi-Fi на ПК...", "info")
            
            # Ожидание восстановления подключения к интернету
            connection_attempts = 0
            max_attempts = 60  # Максимум 60 секунд ожидания
            
            while not is_connected() and connection_attempts < max_attempts:
                time.sleep(1)
                connection_attempts += 1
                if connection_attempts % 10 == 0:  # Каждые 10 секунд
                    queue_log_message(f"Ожидание подключения... ({connection_attempts}/{max_attempts})", "info")
            
            if is_connected():
                queue_log_message("Wi-Fi подключен.", "info")
                time.sleep(2)  # Дополнительное время для стабилизации
                
                # Проверка нового IP
                new_ip = get_external_ip()
                queue_log_message(f"Новый IP: {new_ip}", "info")
                
                # Обновление информации об IP и локации в GUI
                if new_ip != "Недоступно":
                    location = get_location_by_ip(new_ip)
                    signals.ip_location_updated.emit(new_ip, location)
            else:
                queue_log_message("Не удалось восстановить подключение к интернету.", "error")
                
        except Exception as e:
            queue_log_message(f"Ошибка сценария: {e}", "error")

# Вспомогательные функции
def append_full_result(login, password, result):
    all_results.append((login, password, result))

def add_token_separator():
    try:
        with file_lock:
            with open(tokens_file, "a", encoding="utf-8") as f:
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                separator = f"\n{'='*30} {now} {'='*30}\n"
                f.write(separator)
    except Exception as e:
        queue_log_message(f"Ошибка при добавлении разделителя: {e}", "error")

def save_token_to_file(token, is_error=False):
    try:
        with file_lock:
            with open(tokens_file, "a", encoding='utf-8') as file:
                if is_error:
                    file.write(f"Ошибка: {token}\n")
                else:
                    file.write(token + "\n")
        queue_log_message(f"{'Ошибка' if is_error else 'Токен'} сохранён: {tokens_file}", "info")
        if not is_error:
            queue_token_message(token)
    except Exception as e:
        queue_log_message(f"Ошибка сохранения токена: {e}", "error")

def save_failed_login(login, password):
    try:
        with file_lock:
            with open(failed_logins_file, "a", encoding='utf-8') as file:
                file.write(f"{login}:{password}\n")
        queue_log_message(f"Неуспешный логин сохранён: {failed_logins_file}", "info")
        queue_failed_message(login, password)
    except Exception as e:
        queue_log_message(f"Ошибка сохранения логина: {e}", "error")

def read_login_passwords():
    if not os.path.exists(login_pass_file):
        queue_log_message(f"Файл не найден: {login_pass_file}", "error")
        return []
    try:
        with open(login_pass_file, "r", encoding='utf-8') as file:
            accounts = [line.strip().split(":", 1) for line in file if line.strip()]
        return accounts
    except Exception as e:
        queue_log_message(f"Ошибка чтения файла: {e}", "error")
        return []

def is_connected():
    try:
        socket.create_connection(("8.8.8.8", 53), timeout=3)
        return True
    except OSError:
        return False

def get_connected_wifi():
    try:
        output = subprocess.check_output("netsh wlan show interfaces", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and "BSSID" not in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    return ssid if ssid else "Нет подключения"
        return "Нет подключения"
    except Exception as e:
        return f"Ошибка: {e}"

def get_available_networks():
    networks = []
    try:
        output = subprocess.check_output("netsh wlan show networks", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and ":" in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    if ssid and ssid not in networks:
                        networks.append(ssid)
    except Exception as e:
        queue_log_message(f"Ошибка при получении сетей: {e}", "error")
    return networks

def get_external_ip():
    try:
        response = requests.get('https://api.ipify.org', timeout=10)
        response.raise_for_status()
        return response.text.strip()
    except requests.RequestException:
        return "Недоступно"

def get_location_by_ip(ip):
    try:
        response = requests.get(f'https://ipinfo.io/{ip}/json', timeout=10)
        response.raise_for_status()
        data = response.json()
        city = data.get('city', 'Неизвестно')
        region = data.get('region', 'Неизвестно')
        country = data.get('country', 'Неизвестно')
        return f"{city}, {region}, {country}"
    except requests.RequestException:
        return "Недоступно"

def main():
    app = QApplication(sys.argv)
    
    # Установка стиля приложения
    app.setStyle('Fusion')
    
    window = MainWindow()
    window.show()
    
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
